﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using SharpSvn;
using SourceSafeTypeLib;

namespace VssMigrate
{
	internal static class Program
	{
		private static readonly List<IVSSItem> fileList = new List<IVSSItem>();
		private static readonly List<IVSSItem> projList = new List<IVSSItem>();
		private static int nRetCode;
		private static int numFilesHandled;
		private static int numRevisionsHandled;
		private static string outputDIR;
		private static string repoDIR;
		private static string svnPASSWORD;
		private static string svnPROJ;
		private static string svnREVPROPSPATH;
		private static string svnURL;
		private static string svnUSER;
		private static VSSDatabase vssDb;
		private static string vssDIR;
		private static string vssPASSWORD;
		private static string vssPROJ;
		private static string vssSRCSAFEINI;
		private static string vssUSER;

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		private static void Main(string[] argv)
		{
			nRetCode = 0;

			DateTime startTime = DateTime.Now;

			//check command lines
			if (!ReadProperties())
			{
				Console.Write("Bad/missing info in Vss2SvnEnhanced2.exe.config file.\n");
				Console.Write("\n\nPress any key");
				Console.ReadKey();
				Environment.Exit(0);
			}

			Cleanup();

			try
			{
				if (!BuildFileList())
				{
					ExitError();
				}

				if (!CreateDirectories())
				{
					ExitError();
				}

				if (!ImportDirectories())
				{
					ExitError();
				}

				GetAndAddFiles();

				Console.Write(string.Format("\n\nStart time: {0}\n", startTime));
				string now = DateTime.Now.ToString();

				Console.Write(string.Format("End time: {0}\n", now));

				Console.Write("External commands run: 0\n");
				Console.Write(string.Format("Files Migrated: {0}\n", numFilesHandled));
				Console.Write(string.Format("File Revisions Migrated: {0}\n", numRevisionsHandled));
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex);
			}
			finally
			{
				Console.ReadKey();
			}
		}

		private static bool BuildFileList()
		{
			Console.Write("##### Building file list\n");

			string outFile = Path.Combine(outputDIR, "FileList.txt");
			File.Delete(outFile);

			vssDb = new VSSDatabase();
			vssDb.Open(vssSRCSAFEINI, vssUSER, vssPASSWORD);
			VSSItem vssRootItem = vssDb.get_VSSItem(vssPROJ, false);
			if (vssRootItem.Type == (int)VSSItemType.VSSITEM_PROJECT)
			{
				BuildFileList(vssRootItem);
			}
			else
			{
				fileList.Add(vssRootItem);
			}

			if (fileList.Count == 0)
			{
				Console.Write("No results from building file list\n");
				return false;
			}

			return true;
		}

		private static void BuildFileList(IVSSItem fromVssProject)
		{
			projList.Add(fromVssProject);

			IVSSItems childItems = fromVssProject.get_Items(false);
			IEnumerator enumerator = childItems.GetEnumerator();
			while (enumerator.MoveNext())
			{
				var childItem = (IVSSItem)enumerator.Current;
				if (childItem.Type == (int)VSSItemType.VSSITEM_PROJECT)
				{
					BuildFileList(childItem);
				}
				else
				{
					// skip VSS metadata files.
					if (!Path.GetExtension(childItem.Name).Contains("scc"))
					{
						fileList.Add(childItem);
					}
				}
			}
		}

		private static bool CreateDirectories()
		{
			Console.Write("##### Creating directories\n");

			foreach (IVSSItem vssProj in projList)
			{
				string dir = Path.Combine(repoDIR, vssProj.Spec.Substring(2).Replace("/", "\\"));
				Directory.CreateDirectory(dir);
			}
			return true;
		}

		private static bool ImportDirectories()
		{
			var uri = new Uri(string.Format("{0}{1}", svnURL, svnPROJ));
			string dir = Path.Combine(repoDIR, vssPROJ.Substring(2).Replace("/", "\\"));

			using (SvnClient svnClient = GetSvnClient())
			{
				var importArgs = new SvnImportArgs {LogMessage = "Initial import"};
				if (!svnClient.Import(dir, uri, importArgs))
				{
					return false;
				}

				Cleanup();

				var checkOutArgs = new SvnCheckOutArgs {Depth = SvnDepth.Infinity};
				return svnClient.CheckOut(uri, dir, checkOutArgs);
			}
		}

		private static void GetAndAddFiles()
		{
			using (SvnClient svnClient = GetSvnClient())
			{
				int fileNumber = 0;
				foreach (IVSSItem vssFile in fileList)
				{
					numFilesHandled++;

					Console.Write(string.Format("File {0}/{1} : {2}\n", ++fileNumber, fileList.Count, vssFile.Spec));

					IVSSVersions vssVersions = vssFile.get_Versions(0);
					GetAndAddFile(vssFile, vssVersions, svnClient);
				}
			}
		}

		private static void GetAndAddFile(IVSSItem vssFile, IVSSVersions vssVersions, SvnClient svnClient)
		{
			IEnumerator enumerator = vssVersions.GetEnumerator();
			var versions = new List<IVSSVersion>();
			while (enumerator.MoveNext())
			{
				var vssVersion = (IVSSVersion)enumerator.Current;
				if (!string.IsNullOrEmpty(vssVersion.Label) || !string.IsNullOrEmpty(vssVersion.LabelComment))
				{
					continue; //skip labels
				}
				versions.Add(vssVersion);
			}

			versions.Reverse();

			int versionCounter = 0;
			string versionMessage = string.Empty;
			bool isAdd = true;
			foreach (IVSSVersion version in versions)
			{
				if (!string.IsNullOrEmpty(versionMessage))
				{
					Console.Write(new string((char)0x08, versionMessage.Length));
				}
				versionMessage = string.Format("     Version {0}/{1}", ++versionCounter, versions.Count);
				Console.Write(versionMessage);
				GetAndAddFileVersion(vssFile, version, svnClient, isAdd);
				isAdd = false;
			}
			Console.Write("\n");
		}

		private static void GetAndAddFileVersion(IVSSItem vssFile, IVSSVersion vssVersion, SvnClient svnClient,
		                                         bool isAdd)
		{
			numRevisionsHandled++;
			string dir = string.Format("{0}{1}", repoDIR, vssFile.Spec.Substring(1)).Replace("/", "\\");
			dir = Path.GetDirectoryName(dir);
			string filePath = Path.Combine(dir, vssFile.Name);
			VSSItem versionItem = vssFile.get_Version(vssVersion.VersionNumber);
			versionItem.Get(ref filePath, (int)(VSSFlags.VSSFLAG_USERRONO | VSSFlags.VSSFLAG_TIMEMOD));

			VssBindingRemover.RemoveBindings(filePath);

			if (isAdd)
			{
				svnClient.Add(filePath);
			}

			var commitArgs = new SvnCommitArgs {LogMessage = versionItem.VSSVersion.Comment};
			svnClient.Commit(filePath, commitArgs);

			if (!string.IsNullOrEmpty(svnREVPROPSPATH))
			{
				SvnInfoEventArgs infoEventArgs;
				svnClient.GetInfo(SvnTarget.FromString(filePath), out infoEventArgs);
				var revPropsFolder = ((int)Math.Floor(infoEventArgs.Revision / 1000.0)).ToString();
				var propsFile = Path.Combine(Path.Combine(svnREVPROPSPATH, revPropsFolder),
				                             infoEventArgs.Revision.ToString());
				var props = new SvnRevProps(propsFile);
				props.SetAuthor(vssVersion.Username);
				props.SetDate(vssVersion.Date);
				props.Save();
			}

			File.Delete(filePath);
		}

		private static string GetPrivateProfileString(string settingName)
		{
			return ConfigurationManager.AppSettings[settingName];
		}

		private static void Cleanup()
		{
			const string command = "cmd.exe";
			string parms = string.Format("/S /C rmdir /S /Q \"{0}\"", repoDIR);
			var startInfo = new ProcessStartInfo
			                	{
			                		CreateNoWindow = true,
			                		UseShellExecute = false,
			                		FileName = command,
			                		Arguments = parms
			                	};
			var proc = Process.Start(startInfo);
			if (proc != null)
			{
				proc.WaitForExit();
			}
		}

		private static bool ReadProperties()
		{
			//Test whether we can load VSS Interop
			//(i.e. do they have an acceptable version of ssapi.dll registered?)

			vssDIR = GetPrivateProfileString("VSSDIR");
			vssSRCSAFEINI = Path.Combine(vssDIR, "srcsafe.ini");
			if (!File.Exists(vssSRCSAFEINI))
			{
				Console.Write(
					string.Format(
						"VSSDIR does not contain VSS repository (with a srcsafe.ini file).  (Looking for: {0})\n",
						vssSRCSAFEINI));
				return false;
			}

			vssPROJ = GetPrivateProfileString("VSSPROJ");
			if (3 > vssPROJ.Length)
			{
				Console.Write("VSSPROJ is not set (also must have a project name e.g. $/myProject)\n");
				return false;
			}

			vssUSER = GetPrivateProfileString("VSSUSER");
			vssPASSWORD = GetPrivateProfileString("VSSPASSWORD");

			svnUSER = GetPrivateProfileString("SVNUSER");
			svnPASSWORD = GetPrivateProfileString("SVNPASSWORD");

			svnURL = GetPrivateProfileString("SVNURL");
			if (string.IsNullOrEmpty("SVNURL"))
			{
				Console.Write("SVNURL is not set\n");
				return false;
			}
			if (!svnURL.EndsWith("/"))
			{
				svnURL += "/";
			}

			svnPROJ = GetPrivateProfileString("SVNPROJ");
			if (string.IsNullOrEmpty(svnPROJ))
			{
				Console.Write("SVNPROJ is not set (also must have a project name e.g. myProject\n");
				return false;
			}
			if (svnPROJ.StartsWith("/"))
			{
				svnPROJ = svnPROJ.Substring(1);
			}

			svnREVPROPSPATH = GetPrivateProfileString("SVNREVPROPSPATH");
			if (!string.IsNullOrEmpty(svnREVPROPSPATH))
			{
				if (!Directory.Exists(svnREVPROPSPATH))
				{
					Console.Write("SVNREVPROPSPATH not found\n");
					return false;
				}
			}

			outputDIR = GetPrivateProfileString("WORKDIR");
			if (string.IsNullOrEmpty(outputDIR) || !Directory.Exists(outputDIR))
			{
				Console.Write("WORKDIR does not point at an existing directory\n");
				return false;
			}
			repoDIR = Path.Combine(outputDIR, "_migrate");
			Directory.CreateDirectory(repoDIR);
			return true;
		}

		private static SvnClient GetSvnClient()
		{
			var client = new SvnClient();
			client.Authentication.DefaultCredentials = new SvnCredentialProvider(svnUSER, svnPASSWORD);
			return client;
		}

		private static void ExitError()
		{
			Console.Write("\n\nExiting.  Press any key to exit and clean up the working directory");
			Console.ReadKey();
			Cleanup();

			Environment.Exit(nRetCode);
		}
	}
}